package Tools;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Implement function of Model of architecture MVC
 * @author Ivan
 */
public class TModel {

    public enum TKey {
        REPLACE_YES, REPLACE_NO, REPLACE_All, DELETE_YES, DELETE_NO, NONE
    }

    public enum TCommand {

        mkdir, dir, copy, move, tree, find, del, cd
    }

    private TCommand typeOfCommand;
    private String workDirectory;

    public TModel() {
        workDirectory = "C:\\";
    }

    public String getWorkDirectory() {
        return workDirectory;
    }

    public List<String> parsingInputData(String inputOfUser) {
        List<String> parameters = new ArrayList<String>(0);
        String initializedParameter;

        String[] temp = inputOfUser.split("\\s");
        parameters.add(temp[0].toLowerCase(Locale.ENGLISH).intern());

        for (int i = 1; i < temp.length; i++) {
            initializedParameter = this.initialisationOfDirectory(temp[i].intern());
            parameters.add(initializedParameter);

        }
        return parameters;
    }

    private String initialisationOfDirectory(String directory) {

        if (!directory.contains("*")) {
            Pattern pattern = Pattern.compile("([a-zA-Z]{1}:[\\a-zA-Z\\d\\u002E\\u005F]+)");
            Matcher matcher = pattern.matcher(directory);
            if (!matcher.matches()) {
                directory = workDirectory + directory;
            }
        }
        return directory;
    }

    public ACommand initializeCommand(List<String> parameters) throws IOException {
        ACommand command = null;

        if (parameters.get(0) == typeOfCommand.dir.toString()) {
            command = (ACommand) new TDir(typeOfCommand.dir);
        } else if (parameters.get(0) == typeOfCommand.mkdir.toString()) {
            command = (ACommand) new TMkDir(typeOfCommand);
        } else if (parameters.get(0) == typeOfCommand.move.toString()) {
            command = (ACommand) new TMove(typeOfCommand.move);
        } else if (parameters.get(0) == typeOfCommand.copy.toString()) {
            command = (ACommand) new TCopy(typeOfCommand.copy);
        } else if (parameters.get(0) == typeOfCommand.del.toString()) {
            command = (ACommand) new TDel(typeOfCommand.del);
        } else if (parameters.get(0) == typeOfCommand.find.toString()) {
            command = (ACommand) new TSearcher(typeOfCommand.find);
        } else if (parameters.get(0) == typeOfCommand.tree.toString()) {
            command = (ACommand) new TTree(typeOfCommand.tree);
        } else if (parameters.get(0) == typeOfCommand.cd.toString()) {
            command = (ACommand) new TCDir(typeOfCommand.cd);
        }

        if (command == null) {
            throw new IOException("command \"" + parameters.get(0) + "\" in not correct !\n");
        } else {
            parameters.remove(0);
        }

        return command;

    }

    public class TMkDir extends ACommand {

        public TMkDir(TCommand typeOfCommand) {
            super(typeOfCommand);
        }

        private String messageAboutError = "";

        @Override
        public boolean action(List<String> parameters, TKey key)
                throws IOException {

            File file;

            for (String directory : parameters) {
                file = new File(directory);
                file.mkdir();
            }

            return true;


        }

        @Override
        public List<String> setParameters(List<String> parameters)
                throws IOException {
            if ((parameters == null) || (parameters.size() < 1)) {
                throw new IOException("Error in syntax of command!");
            }

            File f;
            List<String> validParameters = new ArrayList<String>(0);

            for (String directory : parameters) {
                f = new File(directory);
                if (f.exists()) {
                    messageAboutError += "Subfolder or file " + f.getName() + " already exist!\n";
                } else {
                    validParameters.add(directory);
                }

            }
            if (messageAboutError.length() != 0) {
                throw new IOException(messageAboutError);
            }

            return validParameters;
        }
    }

    public class TCopy extends ACommand {

        private StringBuffer stringPrintResult;
        private BufferedReader reader;
        private BufferedWriter writer;
        private String destinationDirectory;
        private List<String> processedFiles = new ArrayList<String>(0);

        public TCopy(TCommand typeOfCommand) {
            super(typeOfCommand);
            stringPrintResult = new StringBuffer();
        }

        public boolean action(List<String> parameters, TKey optionOfCopy) throws IOException {
            File fileSource, fileDestination;
            stringPrintResult = new StringBuffer();

            for (String pathname : parameters) {
                if (!processedFiles.contains(pathname)) {

                    fileSource = new File(pathname);
                    fileDestination = new File(destinationDirectory);

                    reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileSource)));

                    if (fileDestination.isDirectory()) {
                        fileDestination = new File(destinationDirectory + "\\" + fileSource.getName());
                    }

                    if ((fileDestination.exists()) && (optionOfCopy != TKey.REPLACE_All)) {

                        if (optionOfCopy == null) {
                            stringPrintResult.append("Replace " + fileDestination.getName() + " [Yes(да) \\ No(нет) \\ All(все)]: ");
                            setPrintResult(stringPrintResult);
                            return false;
                        } else if (optionOfCopy == TKey.REPLACE_YES) {
                            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileDestination)));
                            optionOfCopy = null;
                        } else if (optionOfCopy == TKey.REPLACE_NO) {
                            continue;
                        }
                    } else {
                        writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileDestination)));
                    }

                    while (reader.ready()) {
                        writer.write(reader.readLine());
                    }

                    reader.close();
                    writer.close();
                    processedFiles.add(pathname);
                }
            }

            stringPrintResult.append("Amount of the copied files : " + processedFiles.size() + "\n");
            setPrintResult(stringPrintResult);
            processedFiles = new ArrayList<String>(0);
            return true;
        }

        public List<String> setParameters(List<String> parameters) throws IOException {

            File file;
            List<String> validParameters = new ArrayList<String>(0);

            if ((parameters == null) || (parameters.size() == 0) || (parameters.size() > 2)) {
                throw new IOException("Error of syntaxis of command!\n");
            }

            if (parameters.size() == 1) {
                destinationDirectory = workDirectory;
            } else {
                file = new File(parameters.get(1));
                destinationDirectory = parameters.get(1);
            }

            file = new File(parameters.get(0));
            if (!file.exists()) {
                throw new IOException("didn't could find specief file!\n");
            } else {
                if (file.isDirectory()) {
                    for (File externFile : file.listFiles()) {
                        if (externFile.isFile()) {
                            validParameters.add(externFile.getAbsolutePath());
                        }
                    }
                } else {
                    validParameters.add(parameters.get(0));
                }
            }
            return validParameters;
        }
    }

    public class TMove extends ACommand {

        public TMove(TCommand typeOfCommand) {
            super(typeOfCommand);
        }

        @Override
        public boolean action(List<String> parameters, TKey key) throws IOException {
            boolean result;
            TCopy copy = new TCopy(typeOfCommand.copy);
            TDel del = new TDel(typeOfCommand);

            List<String> temp = new ArrayList<String>();
            temp = copy.setParameters(parameters);
            result = copy.action(temp, TKey.NONE);

            result = del.action(del.setParameters(temp), key.DELETE_YES);
            setPrintResult(new StringBuffer("moved complete\n"));

            return result;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws IOException {

            File fileSource;
            File fileDestination;

            if ((parameters.size() == 0) || (parameters.size() > 2)) {
                throw new IOException("Error in syntax of command!");
            } else {
                if (parameters.size() == 1) {
                    fileDestination = new File(workDirectory);
                } else {
                    fileDestination = new File(parameters.get(1));
                }
                fileSource = new File(parameters.get(0));

                if (!fileSource.exists()) {
                    throw new IOException("didn't could find source file!\n");
                } else {
                    if (!fileSource.isDirectory()) {
                        if (fileDestination.isDirectory()) {
                            for (File externFile : fileDestination.listFiles()) {
                                if (externFile.getName() == fileSource.getName()) {
                                    throw new IOException("File is exist\n");
                                }
                            }
                        } else if (fileDestination.getName() == fileSource.getName()) {
                            throw new IOException("File is exist\n");
                        }
                    } else {
                        throw new IOException("access is denied\n");
                    }
                }
            }
            return parameters;
        }
    }

    public class TDel extends ACommand {

        private List<String> processedFiles;
        private List<String> deletedFiles;
        private StringBuffer stringPrintResult;

        public TDel(TCommand typeOfCommand) {
            super(typeOfCommand);
            processedFiles = new ArrayList<String>(0);
            deletedFiles = new ArrayList<String>(0);
            stringPrintResult = new StringBuffer();
        }

        @Override
        public boolean action(List<String> parameters, TKey key) throws IOException {

            File file;
            boolean isDeleted;
            stringPrintResult = new StringBuffer();
            for (String puthName : parameters) {
                if (!processedFiles.contains(puthName) && (key != TKey.DELETE_NO)) {

                    file = new File(puthName);
                    if (!file.exists()) {
                        stringPrintResult.append("didn't could find file " + file.getAbsolutePath() + "!\n");
                        setPrintResult(stringPrintResult);
                    } else {
                        if (file.isDirectory()) {
                            if (key == TKey.DELETE_YES) {
                                for (File subfile : file.listFiles()) {
                                    isDeleted = subfile.delete();
                                    if (!isDeleted) {
                                        stringPrintResult.append("file : " + subfile.getAbsolutePath() + "delete access denied\n");
                                    } else {
                                        deletedFiles.add(subfile.getAbsolutePath());
                                    }
                                }
                                key = null;
                                processedFiles.add(puthName);

                            } else {
                                stringPrintResult.append(file.getAbsolutePath() + "\\*  , Continue [Yes(да) \\ No(нет)] ?  ");
                                setPrintResult(stringPrintResult);
                                return false;

                            }

                        } else {
                            isDeleted = file.delete();
                            if (!isDeleted) {
                                stringPrintResult.append("file : " + file.getAbsolutePath() + "delete access denied\n");
                            } else {
                                deletedFiles.add(file.getAbsolutePath());

                            }
                            key = null;
                            processedFiles.add(puthName);
                        }
                    }
                } else if ((key == TKey.DELETE_NO) && !processedFiles.contains(puthName)) {
                    key = null;
                    processedFiles.add(puthName);
                }

            }

            stringPrintResult.append("Deleted : " + deletedFiles.size() + " files\n");
            processedFiles.clear();
            deletedFiles.clear();
            setPrintResult(stringPrintResult);
            return true;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws IOException {
            if (parameters.size() == 0) {
                throw new IOException("Error of syntaxis of command!\n");
            } else {
                return parameters;
            }
        }
    }

    public class TDir extends ACommand {

        public StringBuffer stringPrintResult = new StringBuffer();

        public TDir(TCommand typeOfCommand) {
            super(typeOfCommand);
        }

        public void browseDirectory(File directoty, StringBuffer stringPrintResult) {
            if (directoty.isDirectory()) {
                for (File subFile : directoty.listFiles()) {
                    if (subFile.isDirectory()) {
                        stringPrintResult.append(subFile.getName() + "\t <DIR> \t" + new Date(subFile.lastModified()).toString() + "\n");
                    } else {
                        stringPrintResult.append(subFile.getName() + "\t       \t" + new Date(subFile.lastModified()).toString() + "\n");
                    }
                }
            } else {
                stringPrintResult.append(directoty.getName() + "\t       \t" + new Date(directoty.lastModified()).toString() + "\n");
            }

        }

        /**
         * This method displays the content of the directory which specified as parameter.
         * @param parameters
         * @return
         * @throws Exception
         */
        @Override
        public boolean action(List<String> parameters, TKey key)throws IOException {
            File root;
            for (String puthName : parameters) {
                //Initialization of root directory
                root = new File(puthName);

                if (root.isDirectory()) {
                    stringPrintResult.append("Contens of folder  " + puthName + " :\n");

                    //Print result
                    browseDirectory(root, stringPrintResult);

                    stringPrintResult.append("\n");
                }

            }

            setPrintResult(stringPrintResult);
            return true;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws IOException {

            List<String> validParameters = new ArrayList<String>(0);
            File file;

            if (parameters.size() == 0) {
                validParameters.add(workDirectory);
            } else {
                for (String puthName : parameters) {
                    file = new File(puthName);
                    if (file.exists()) {
                        if (file.isDirectory()) {
                            validParameters.add(puthName);
                        } else {
                            stringPrintResult.append("File " + puthName + " is not folder!\n");
                        }

                    } else {
                        stringPrintResult.append("File " + puthName + " is no found!\n");
                    }
                }
            }
            return validParameters;
        }
    }

    public class TCDir extends ACommand {

        public TCDir(TCommand typeOfCommand) {
            super(typeOfCommand);

        }

        public void changeWorkDirectory(String newDirectory) throws IOException {

            File temp = new File(newDirectory);

            if (!temp.isDirectory()) {
                throw new IOException("Directory  \"" + newDirectory + "\" is not exist!\n");
            } else {
                workDirectory = newDirectory.concat("\\");
            }
        }

        @Override
        public boolean action(List<String> parameters, TKey key) throws IOException {

            File temp = new File(parameters.get(0));

            if (!temp.isDirectory()) {
                throw new IOException("Directory  \"" + parameters.get(0) + "\" is not exist!\n");
            } else {
                workDirectory = parameters.get(0).concat("\\");
            }

            setPrintResult(new StringBuffer("work directory was changed\n"));
            return true;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws IOException {

            File file;
            IOException e = new IOException("system can not find specief path \n");
            if (parameters.size() > 1) {
                throw e;
            } else {
                file = new File(parameters.get(0));
                if (!file.exists()) {
                    throw e;
                }
            }

            return parameters;
        }
    }

    public class TTree extends ACommand {

        private StringBuffer stringPrintResult;

        public TTree(TCommand typeOfCommand) {
            super(typeOfCommand);
            stringPrintResult = new StringBuffer();
        }
       
        //Recursive method search in depth with display of tree
        private void depthSearchPrint(File root, String strTab) {

            String strClildNode = strTab + "└────";
            String fileName = null;

            for (File file : root.listFiles()) {

                fileName = file.getName();

                stringPrintResult.append(strClildNode + fileName + "\n");
                if ((file.listFiles() != null) && (file.listFiles().length != 0)) {
                    strTab += "\t";
                    depthSearchPrint(file, strTab);
                }
            }
        }

        /**
         * This method  displays the directory tree and theirs content in console window.
         * Name of initial directory transfered as input parameter.
         * @param parameters
         * @return
         * @throws Exception
         */
        @Override
        public boolean action(List<String> parameters, TKey key) throws IOException {
            //Initialization of root directory
            File root = new File(parameters.get(0));

            //List of all files which were found in specified directory
            List<File> nodes = new ArrayList<File>(0);
            nodes = Arrays.asList(root.listFiles());

            String tree = null;
            ///Display of tree
            for (File file : nodes) {
                tree = "├────" + file.getName() + "\n";
                //System.out.print(tree);
                stringPrintResult.append(tree);

                if (file.isDirectory()) {
                    depthSearchPrint(file, "|\t");
                }
            }

            setPrintResult(stringPrintResult);
            return true;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws IOException {

            if (parameters.size() > 1) {
                throw new IOException("So many parameters!");
            } else if (parameters.size() == 0) {
                parameters.add(workDirectory);
            }

            File file = new File(parameters.get(0));

            if (file.exists() && file.isDirectory()) {
                return parameters;
            } else {
                throw new IOException("Path specief incorrect : " + parameters.get(0) + "\nSubfolders is not exist!");
            }

        }
    }

    public class TSearcher extends ACommand {

        private TMaskCaseSensitive filter;
        private StringBuffer stringPrintResult;
        private String direcotyOfSearch;

        public TSearcher(TCommand typeOfCommand) {
            super(typeOfCommand);

            stringPrintResult = new StringBuffer();
        }
        
        //Recursive method search in depth
        private void depthSearch(File root, ArrayList<File> AllFiles) {
            for (File node : root.listFiles()) {
                AllFiles.add(node);
                if ((node.listFiles() != null) && (node.listFiles().length != 0)) {
                    depthSearch(node, AllFiles);
                }
            }

        }

        /**
         *This method displays the content of the directory with extended information using searching mask.
         *Directory and mask of search specified as parameters this methods.
         * @param parameters
         * @return
         * @throws Exception
         */
        @Override
        public boolean action(List<String> parameters, TKey key) throws IOException {

            TDir dir = new TDir(typeOfCommand.dir);
            //Initialization of root directory
            File root = new File(direcotyOfSearch);

            //List of all files which were found in specified directory
            ArrayList<File> all_found_files = new ArrayList<File>(0);

            //List of files which satisfy requirement mask of searching
            ArrayList<File> processed_files = new ArrayList<File>(0);

            //Search all files
            depthSearch(root, all_found_files);

            // Verification on compliance
            for (File file : all_found_files) {
                if (filter.match(file)) {
                    processed_files.add(file);
                }
            }

            //Print result
            System.out.print("(Amount of found files : " + processed_files.size() + ") \n");
            for (File file : processed_files) {
                dir.browseDirectory(file, stringPrintResult);

            }

            setPrintResult(stringPrintResult);
            return true;
        }

        @Override
        public List<String> setParameters(List<String> parameters) throws Exception {
            if ((parameters.size() == 0) || (parameters.size()) > 2) {
                throw new IOException("Mack specified incorrect!\n");
            } else {
                filter = new TMaskCaseSensitive(parameters.get(0));
                if (parameters.size() == 1) {
                    direcotyOfSearch = workDirectory;
                } else {
                    direcotyOfSearch = parameters.get(1);
                }

            }

            return null;
        }
    }

    /**
     * This class implements initialization of  mask of searching.
     * @author Ivan
     *
     */
    public class TMaskCaseSensitive {
        private final String FILTER_MASK = "((\\*){1}[a-zA-Z\\d\\s\\u005F]+(\\*)?)|((\\*){1}[a-zA-Z\\d\\s\\u005F]+(\\*){1})|((\\*)?[a-zA-Z\\d\\s\\u005F]+(\\*){1})";
        private String filter;
        private Pattern pattern;
        private Matcher matcher;
        private String mask;

        public TMaskCaseSensitive(String mask) throws PatternSyntaxException, InitializationMaskException {
            this.mask = mask;

            //initialization of pattern
            pattern = Pattern.compile(FILTER_MASK);
            matcher = pattern.matcher(mask);

            //validation of the entered search mask
            if (!matcher.matches()) {
                throw new InitializationMaskException("mask entered incorrect!");
            }
        }

        public boolean match(File f) {

            String targetStr = "";
            for (Character ch : mask.toCharArray()) {
                if (ch != '*') {
                    targetStr += ch;
                }
            }

            if (mask.startsWith("*") && mask.endsWith("*")) {
                filter = "([a-zA-Z\\d\\s\\u005F\\u002E]+)?(" + targetStr + ")([a-zA-Z\\d\\s\\u005F\\u002E]+)?";
            } else if (mask.startsWith("*")) {
                filter = "[a-zA-Z\\d\\s\\u005F\\u002E]+(" + targetStr + ")";
            } else if (mask.endsWith("*")) {
                filter = "(" + targetStr + ")[a-zA-Z\\d\\s\\u005F\\u002E]+";
            }

            //initialization of pattern
            pattern = Pattern.compile(filter);
            matcher = pattern.matcher(f.getName());

            //check the file name in accordance with the search mask
            if (matcher.matches()) {
                return true;
            } else {
                return false;
            }
        }

        /**
         * Class of exception, which called in case incorrect enter value of mask of search.
         * @author Ivan
         */
        public class InitializationMaskException extends Exception {

            private static final long serialVersionUID = 1L;

            public InitializationMaskException(String message) {
                super(message);
            }
        }
    }
}
